package com.yan.core.servlet;

import com.alibaba.fastjson.JSON;
import com.yan.core.annotations.*;
import com.yan.core.handler.HandlerMapping;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Pattern;

public class YanDispatcherServlet extends HttpServlet {

    private Properties properties = new Properties();

    //缓存扫描到的类的全限定类名
    private List<String> classNames = new ArrayList<>();

    private Map<String, Object> ioc = new HashMap<>();

    //存url和method之间的映射关系
//    private Map<String, Method> handlerMapping = new HashMap<>();
    List<HandlerMapping> handlerMappings = new ArrayList<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        String requestURI = req.getRequestURI();
//        Method method = handlerMapping.get(requestURI);
//        method.invoke()
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        HandlerMapping handler = getHandler(req.getRequestURI());
        if (handler == null) {
            resp.getWriter().write("404 找不到 接口");
            return;
        }
        //校验权限
        Set<String> security = handler.getSecurity();
        if (!security.isEmpty()) {
            String name = req.getParameter("name");
            if (name == null || name.trim().length() == 0 || !security.contains(name)) {
                resp.getWriter().write("403 无权限");
                return;
            }
        }

        //定义反射参数
        Object[] args = new Object[handler.getMethod().getParameterTypes().length];
        //填充参数
        handlerParameter(args, handler, req, resp);
        //最终调用handler的method属性
        try {
            Object invoke = handler.getMethod().invoke(handler.getController(), args);
            if (invoke == null) {
                resp.getWriter().write("无任务返回");
                return;
            } else {
                resp.getWriter().write(JSON.toJSONString(invoke));
                return;
            }
        } catch (
                IllegalAccessException e) {
            e.printStackTrace();
        } catch (
                InvocationTargetException e) {
            e.printStackTrace();
        }

    }

    private HandlerMapping getHandler(String requestURI) {
        if (handlerMappings.isEmpty()) {
            return null;
        }
        for (HandlerMapping handlerMapping : handlerMappings) {
            //匹配请求url
            if (Pattern.matches(handlerMapping.getMethodUrl(), requestURI)) {
                return handlerMapping;
            }
        }
        return null;
    }

    private void handlerParameter(Object[] args, HandlerMapping handler, HttpServletRequest req, HttpServletResponse resp) {
        Map<String, String[]> parameterMap = req.getParameterMap();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            String parameterName = entry.getKey();
            String parameterValue = StringUtils.join(entry.getValue(), ",");

            Integer index = handler.getParamIndexMapping().get(parameterName);
            if (index != null && index > -1) {
                args[index] = parameterValue;
            }
        }

        //填充 request
        Integer requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        if (requestIndex != null && requestIndex > -1) {
            args[requestIndex] = req;
        }
        //填充 response
        Integer responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        if (responseIndex != null && responseIndex > -1) {
            args[responseIndex] = resp;
        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {

        //1、加载配置文件 springMvc.properties
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);

        //2、扫描相关的类，扫描注解
        doScan(properties.getProperty("scanPackage"));

        //3、初始化bean对象（实现ioc容器，基于注解）
        doInstance();

        //4、实现依赖注入
        doAutoWired();

        //5、构造一个handlerMapping处理映射器，将配置好的url 和method建立映射关系
        initHandlerMapping();

//        System.out.println("-------handlerMappings---------" + JSON.toJSONString(handlerMappings));

        System.out.println("yan mvc 初始化完成");
        //等待请求进入，处理请求
    }

    //1、加载配置文件 springMvc.properties
    private void doLoadConfig(String contextConfigLocation) {
        if (contextConfigLocation != null) {
            contextConfigLocation = contextConfigLocation.replaceAll("classpath:", "");
        }
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //2、扫描相关的类，扫描注解  scanPackage ->  com.yan.demo
    private void doScan(String scanPackage) {
        String filePath = this.getClass().getResource("/").getPath() + scanPackage.replaceAll("\\.", "/");
        File file = new File(filePath);
        File[] files = file.listFiles();
        for (File childFile : files) {
            if (childFile.isDirectory()) {
                //递归 com.yan.project.controller
                doScan(scanPackage + "." + childFile.getName());
            } else if (childFile.getName().endsWith(".class")) {
                String className = scanPackage + "." + childFile.getName().replaceAll(".class", "");
                classNames.add(className);
            }
        }
    }

    //3、初始化bean对象（实现ioc容器，基于注解）
    private void doInstance() {
        if (classNames.size() == 0) {
            return;
        }
        try {
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(YanController.class)) {
                    String simpleName = clazz.getSimpleName();//DemoController
                    String controllerName = lowerCase(simpleName);//demoController
                    ioc.put(controllerName, clazz.newInstance());
                } else if (clazz.isAnnotationPresent(YanService.class)) {
                    YanService annotation = clazz.getAnnotation(YanService.class);
                    String serviceName = annotation.value();
                    if (serviceName == null || "".equals(serviceName.trim())) {
                        serviceName = lowerCase(clazz.getSimpleName());
                    }
                    Object service = clazz.newInstance();
                    ioc.put(serviceName, service);
                    //service 的 所有接口
                    Class<?>[] serviceInterfaces = clazz.getInterfaces();
                    for (Class<?> serviceInterface : serviceInterfaces) {
                        String serviceInterfaceName = lowerCase(serviceInterface.getSimpleName());
                        ioc.put(serviceInterfaceName, service);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //4、实现依赖注入
    private void doAutoWired() {
        if (ioc.isEmpty()) {
            return;
        }
        //便利ioc中所有对象，查看对象中的字段，是否有@YanAutowired注解，如果需要维护依赖注入关系
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Object object = entry.getValue();
            Field[] fields = object.getClass().getDeclaredFields();
            for (Field field : fields) {
                if (!field.isAnnotationPresent(YanAutowired.class)) {
                    continue;
                }
                YanAutowired annotation = field.getAnnotation(YanAutowired.class);
                String autowiredName = annotation.value();
                if (autowiredName == null || "".equalsIgnoreCase(autowiredName.trim())) {
                    autowiredName = lowerCase(field.getType().getSimpleName());
                }
                field.setAccessible(true);
                try {
                    field.set(object, ioc.get(autowiredName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    //5、构造一个handlerMapping处理映射器，将配置好的url 和method建立映射关系
    private void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Object controller = entry.getValue();
            Class<?> clazz = controller.getClass();
            if (!clazz.isAnnotationPresent(YanController.class)) {
                continue;
            }
            String baseUrl = "";
            if (clazz.isAnnotationPresent(YanRequestMapping.class)) {
                YanRequestMapping annotation = clazz.getAnnotation(YanRequestMapping.class);
                baseUrl = annotation.value().trim();
            }

            Set<String> baseSecurity = new HashSet<>();
            if (clazz.isAnnotationPresent(YanSecurity.class)) {
                YanSecurity annotation = clazz.getAnnotation(YanSecurity.class);
                for (String value : annotation.value()) {
                    if (value != null && value.trim().length() > 0) {
                        baseSecurity.add(value);
                    }
                }
            }
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(YanRequestMapping.class)) {
                    continue;
                }
                YanRequestMapping annotation = method.getAnnotation(YanRequestMapping.class);
                String methodUrl = baseUrl + annotation.value().trim();
                HandlerMapping handlerMapping = new HandlerMapping(controller, method, methodUrl);

                if ("/demo/index".equalsIgnoreCase(methodUrl)) {
                    System.out.println(methodUrl);
                }
                Parameter[] parameters = method.getParameters();
                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    Class<?> type = parameter.getType();
                    if (type == HttpServletRequest.class || type == HttpServletResponse.class) {
                        handlerMapping.getParamIndexMapping().put(type.getSimpleName(), i);
                    } else {
                        handlerMapping.getParamIndexMapping().put(parameter.getName(), i);
                    }
                }

                handlerMapping.getSecurity().addAll(baseSecurity);
                YanSecurity yanSecurity = method.getAnnotation(YanSecurity.class);
                if (yanSecurity != null) {
                    for (String value : yanSecurity.value()) {
                        if (value != null && value.trim().length() > 0) {
                            handlerMapping.getSecurity().add(value);
                        }
                    }
                }

                handlerMappings.add(handlerMapping);

            }

        }
    }
    /*private void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(YanController.class)) {
                continue;
            }
            String baseUrl = "";
            if (clazz.isAnnotationPresent(YanRequestMapping.class)) {
                YanRequestMapping annotation = clazz.getAnnotation(YanRequestMapping.class);
                baseUrl = annotation.value().trim();
            }
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(YanRequestMapping.class)) {
                    continue;
                }
                YanRequestMapping annotation = method.getAnnotation(YanRequestMapping.class);
                String methodUrl = baseUrl + annotation.value().trim();
                handlerMapping.put(methodUrl, method);
            }

        }
    }*/

    //首字母小写
    private String lowerCase(String simpleName) {
        if (simpleName != null && simpleName.length() > 1) {
            char[] chars = simpleName.toCharArray();
            if (chars[0] >= 'A' && chars[0] <= 'Z') {
                chars[0] = (char) (chars[0] + 32);
            }
            return String.valueOf(chars);
        }
        return null;
    }

}
